// xcachetools
//create by kang
//万能缓存清理工具
package main

import (
	//"bufio"
	"bytes"
	"color"
	"crypto/md5"
	"encoding/hex"
	//"encoding/json"
	"flag"
	"fmt"
	//"io"
	"io/ioutil"
	"log"
	"memcache"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	//"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/goconfig"
	"github.com/gosexy/redis"
)

type Conf struct {
	ListenAddress string
	ProxyIp       string
	DILianUrl     string
	DiLianToken   string
	DLPushUrl     string
	DLCaptcha     string
	WsUrl         string
	WsUser        string
	WsPass        string
	InmQuery      string
	//ThreadMax     int64
}

func GetConfig(cfpath string) (c *Conf) {
	flag.Parse()
	gr, err := goconfig.ReadConfigFile(cfpath)
	if err != nil {
		fmt.Println("ERR:load xcachetools.conf config filure!")
		fmt.Println(err)
		os.Exit(1)
	}
	c = &Conf{}
	var Section string = "GLOBAL"
	c.ListenAddress, _ = gr.GetString(Section, "ListenAddress")
	c.ProxyIp, _ = gr.GetString(Section, "ProxyIp")
	//c.ThreadMax, _ = gr.GetInt64(Section, "ThreadMax")
	c.DILianUrl, _ = gr.GetString(Section, "DILianUrl")
	c.DiLianToken, _ = gr.GetString(Section, "DiLianToken")

	c.DLPushUrl, _ = gr.GetString(Section, "DLPushUrl")
	c.DLCaptcha, _ = gr.GetString(Section, "DLCaptcha")

	c.WsUrl, _ = gr.GetString(Section, "WsUrl")
	c.WsUser, _ = gr.GetString(Section, "WsUser")
	c.WsPass, _ = gr.GetString(Section, "WsPass")
	c.InmQuery, _ = gr.GetString(Section, "InmQuery")
	return c
}

//string to md5
func StrtoMd5(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	x := h.Sum(nil)
	cc := hex.EncodeToString(x)
	return cc
}

// get 请求URL
func ReqUrl(turl, methd, xhost string) (*http.Response, error) {
	request, _ := http.NewRequest(methd, turl, nil)
	request.Header.Set("User-Agent", "PConline-xcachetools")
	request.Host = xhost
	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(netw, proxy string) (net.Conn, error) {
				deadline := time.Now().Add(10 * time.Second)
				c, err := net.DialTimeout(netw, proxy, time.Second*5)
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}
	return client.Do(request)
}

//proxy is atsip
func GetByProxy(url_addr, proxy_addr, method string) (*http.Response, error) {
	proxy_addr = "http://" + proxy_addr
	request, _ := http.NewRequest(method, url_addr, nil)
	request.Header.Set("User-Agent", "xcachetools")
	proxy, err := url.Parse(proxy_addr)
	if err != nil {
		return nil, err
		log.Printf("ERR:GetByProxy err,ip:%s,err:\n", proxy_addr, err)
	}
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxy),
			Dial: func(netw, proxy string) (net.Conn, error) {
				deadline := time.Now().Add(5 * time.Second)
				c, err := net.DialTimeout(netw, proxy, time.Second*5)
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}
	return client.Do(request)
}

func ClsAts(tsip, turl []string) {
	if len(turl) < 1 {
		clsresult = clsresult + fmt.Sprintf("INFO:url is null")
		return
	}
	for _, xurl := range turl {
		for _, ip := range tsip {
			resp, err := GetByProxy(xurl, ip, "PURGE")
			if err != nil {
				log.Printf("ERR:can't connect ip=%s,uri=%s\n", ip, xurl)
				clsresult = clsresult + fmt.Sprintf("ERR:can't connect ip=%s,uri=%s\n", ip, xurl)
				continue
			} else {
				defer resp.Body.Close()
				switch {
				case resp.StatusCode == 200:
					clsresult = clsresult + fmt.Sprintf("INFO:DelSuccess %s,ip=%s\n", xurl, ip)
				case resp.StatusCode == 404:
					clsresult = clsresult + fmt.Sprintf("INFO:NotFound %s,ip=%s\n", xurl, ip)
				default:
					clsresult = clsresult + fmt.Sprintf("WARN:Failure,StatusCode=%d,atsip=%s,uri=%s", resp.StatusCode, ip, xurl)
				}
			}
		}
	}

}

func ClsWangSu(proxyip, cdnintf, pushuser, pushpasswd string, xurl []string) {
	if len(xurl) <= 0 {
		log.Println("clean url to wangsu is null")
		clsresult = clsresult + fmt.Sprintf("clean url to wangsu is null")
		return
	}
	var allurl string
	var allurl1 string
	for _, url := range xurl {
		//uri := strings.TrimLeft(url, "http://")
		uri := strings.Replace(url, "http://", "", 1)
		uri1 := strings.Replace(uri, "&", "%26", -1)
		allurl = allurl + uri + ";"
		allurl1 = allurl1 + uri1 + ";"
	}
	turl := cdnintf + "username=" + pushuser + "&passwd=" + StrtoMd5(pushuser+pushpasswd+strings.TrimRight(allurl, ";")) + "&url=" + strings.TrimRight(allurl1, ";")

	var resp *http.Response
	var err error
	resp, err = GetByProxy(turl, proxyip, "GET")
	if err != nil {
		clsresult = clsresult + fmt.Sprintf("ERROR:call cdn api fail!\n")
		return
	}
	defer resp.Body.Close()
	bodyBytes, _ := ioutil.ReadAll(resp.Body)
	bodycontent := string(bodyBytes)
	clsresult = clsresult + fmt.Sprintf("CDNINFO:SUCCESS,%s,StatusCode=%d,uri=%s\n", bodycontent, resp.StatusCode, turl)

}

//dilian clean CDN cache
func PostByProxy(url_addr, proxy_addr, method string, body *bytes.Reader) (*http.Response, error) {
	proxy_addr = "http://" + proxy_addr
	request, _ := http.NewRequest(method, url_addr, body)
	request.Header.Set("User-Agent", "xcachetools_daemon")
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
	proxy, err := url.Parse(proxy_addr)
	if err != nil {
		return nil, err
		log.Printf("ERR:GetByProxy err,ip:%s,err:\n", proxy_addr, err)
	}
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxy),
			Dial: func(netw, proxy string) (net.Conn, error) {
				deadline := time.Now().Add(5 * time.Second)
				c, err := net.DialTimeout(netw, proxy, time.Second*5)
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}
	return client.Do(request)
}

//DiLian CDN
func ClsDiLian(proxyip, cdnintf, token string, xurl []string) {
	if len(xurl) <= 0 {
		log.Println("clean url to dilian is null")
		clsresult = clsresult + fmt.Sprintf("clean url to dilian is null")
		return
	}
	var allurl string
	var err1 error
	for _, url := range xurl {
		allurl = allurl + url + ","
	}
	PostContent := "captcha=" + token + "&type=1&url=" + strings.TrimRight(allurl, ",")
	var res *http.Response
	body := bytes.NewBuffer([]byte(PostContent))
	isproxy := (proxyip != "")
	if isproxy {
		res, err1 = PostByProxy(cdnintf, proxyip, "POST", bytes.NewReader([]byte(PostContent)))
	} else {
		res, err1 = http.Post(cdnintf, "application/x-www-form-urlencoded; param=value", body)
	}
	if err1 != nil {
		fmt.Println("connect dilianapi error!")
		clsresult = clsresult + fmt.Sprintf("CDNINFO:FAIL,connect dilian api error!\n")
		return
	}
	result, err := ioutil.ReadAll(res.Body)
	if err != nil {
		//log.Println(err)
		clsresult = clsresult + fmt.Sprintf("CDNINFO:FAIL,dilian api error!\n")
		fmt.Println("dilian api error!")
		return
	}
	res.Body.Close()
	//fmt.Printf("%s\n", result)
	if strings.Contains(fmt.Sprintf("%s\n", result), "<code>200") {
		//clsresult = clsresult + fmt.Sprintf("CDNINFO:SUCCESS,%s,uri=%s\n", result, allurl)
		clsresult = clsresult + fmt.Sprintf("CDNINFO:SUCCESS,uri=%s\n", allurl)
	} else {
		//clsresult = clsresult + fmt.Sprintf("CDNINFO:FAIL,%s,uri=%s\n", result, allurl)
		clsresult = clsresult + fmt.Sprintf("CDNINFO:FAIL,uri=%s\n", allurl)
	}
}

func ClsNgx(xhost, tturl []string) {
	if len(tturl) <= 0 {
		log.Println("nginx url is null")
		clsresult = clsresult + fmt.Sprintf("nginx url is null")
		return
	}
	for _, turl := range tturl {
		for _, host := range xhost {
			i, err := url.Parse(turl)
			if err != nil {
				return
			}
			uri := i.RequestURI()
			domain := i.Host
			ngxurl := "http://" + host + "/purge" + uri
			rep, err1 := ReqUrl(ngxurl, "GET", domain)
			if err1 != nil {
				return
			}
			//fmt.Println(uri, domain, ngxurl)
			switch rep.StatusCode {
			case 200:
				clsresult = clsresult + fmt.Sprintf("INFO:DelSuccess %s,ip=%s\n", turl, host)
			case 404:
				clsresult = clsresult + fmt.Sprintf("INFO:NotFound %s,ip=%s\n", turl, host)
			default:
				clsresult = clsresult + fmt.Sprintf("ERR:Failure %s,ip=%s\n", turl, host)
			}
		}
	}

}

func ClsHcs(xhost, tturl []string) {
	if len(tturl) <= 0 {
		log.Println("hcs url is null")
		clsresult = clsresult + fmt.Sprintf("hcs url is null")
		return
	}
	for _, turl := range tturl {
		for _, host := range xhost {
			i, err := url.Parse(turl)
			if err != nil {
				return
			}
			uri := i.RequestURI()
			domain := i.Host
			hcsurl := "http://" + host + uri
			rep, err := ReqUrl(hcsurl, "DELETE", domain)
			if err != nil {
				return
			}
			switch rep.StatusCode {
			case 200:
				clsresult = clsresult + fmt.Sprintf("INFO:DelSuccess %s,ip=%s\n", turl, host)
			case 404:
				clsresult = clsresult + fmt.Sprintf("INFO:NotFound %s,ip=%s\n", turl, host)
			default:
				clsresult = clsresult + fmt.Sprintf("ERR:Failure %s,ip=%s\n", turl, host)
			}
		}
	}
}

func ClsMc(xhost, xkey []string) {
	if len(xkey) <= 0 {
		log.Println("mc key is null")
		clsresult = clsresult + fmt.Sprintf("mc key is null")
		return
	}
	for _, host := range xhost {
		mclient := memcache.New(host)
		xip := strings.Split(host, ":")[0]
		xport := strings.Split(host, ":")[1]
		for _, key := range xkey {
			if key == "flush_all" {
				runout := exec.Command("/bin/bash", "-c", "echo flush_all|nc "+xip+" "+xport)
				buf, err := runout.Output()
				if err != nil {
					clsresult = clsresult + fmt.Sprintf("INFO:%s,ip=%s,Delfail:%s\n", key, host, string(buf))
				} else {
					clsresult = clsresult + fmt.Sprintf("INFO:%s,ip=%s,DelSuccess:%s\n", key, host, string(buf))
				}
			} else {
				err := mclient.Delete(key)
				if err != nil {
					clsresult = clsresult + fmt.Sprintf("INFO:ConnFail or NotFound %s,ip=%s\n", key, host)
				} else {
					clsresult = clsresult + fmt.Sprintf("INFO:DelSucsse %s,ip=%s\n", key, host)
				}
			}
		}
	}
}

func ClsRedis(xhost, xkey []string) {
	if len(xkey) <= 0 {
		log.Println("redis key is null")
		clsresult = clsresult + fmt.Sprintf("redis key is null")
		return
	}
	for _, key := range xkey {
		for _, host := range xhost {
			ip := strings.Split(host, ":")[0]
			xport := strings.Split(host, ":")[1]
			client := redis.New()
			port, _ := strconv.Atoi(xport)
			T_out := time.Duration(time.Second * 10)
			err := client.ConnectWithTimeout(ip, uint(port), T_out)
			if err != nil {
				clsresult = clsresult + fmt.Sprintf("connect redis-server failure\n")
				return
			}
			if key == "flush_all" {
				rst, err := client.FlushDB()
				if err != nil {
					clsresult = clsresult + fmt.Sprintf("INFO:flush success %s,ip=%s,n=%s\n", key, host, rst)
				} else {
					clsresult = clsresult + fmt.Sprintf("INFO:flush fail %s,ip=%s\n", key, host)
				}
			} else {
				n, err := client.Del(key)
				if err != nil {
					clsresult = clsresult + fmt.Sprintf("INFO:Del fail %s,ip=%s,n=%d\n", key, host, n)
				} else {
					clsresult = clsresult + fmt.Sprintf("INFO:Del success %s,ip=%s,n=%d\n", key, host, n)
				}
			}
		}
	}
}
func ClsSquid(xhost, tturl []string) {
	if len(tturl) <= 0 {
		log.Println("squid url is null")
		clsresult = clsresult + fmt.Sprintf("squid url is null")
		return
	}
	for _, turl := range tturl {
		for _, host := range xhost {
			i, err := url.Parse(turl)
			if err != nil {
				return
			}
			uri := i.RequestURI()
			domain := i.Host
			hcsurl := "http://" + host + uri
			rep, err := ReqUrl(hcsurl, "PURGE", domain)
			if err != nil {
				fmt.Println("cls squid cache ip deny")
				clsresult = clsresult + fmt.Sprintf("ERR:Not Allow %s,ip=%s\n", turl, host)
				//return
			} else {
				switch rep.StatusCode {
				case 200:
					clsresult = clsresult + fmt.Sprintf("INFO:DelSuccess %s,ip=%s\n", turl, host)
				case 404:
					clsresult = clsresult + fmt.Sprintf("INFO:NotFound %s,ip=%s\n", turl, host)
				default:
					clsresult = clsresult + fmt.Sprintf("ERR:Failure %s,ip=%s\n", turl, host)
				}
			}

		}
	}
}

func PostResult(result string, taskid int64) {
	var resp *http.Response
	var err error
	xtaskid := strconv.Itoa(int(taskid))
	//替换为网页换行符
	result1 := strings.Replace(result, "\n", "<br>", -1)
	sqlquery := "UPDATE clear_cache SET result='" + result1 + "',status='2' where id='" + xtaskid + "';"
	resp, err = http.PostForm(InmQuery, url.Values{"sql": {sqlquery}})
	if err != nil {
		log.Println("ERR:" + strconv.Itoa(resp.StatusCode) + ",sql:" + sqlquery)
	} else {
		log.Println("INFO:" + strconv.Itoa(resp.StatusCode) + ",sql:" + sqlquery)
		log.Println("clean result submit success")
	}
	defer resp.Body.Close()
	//clsresult = ""
}

func Help() {
	color.Red("Version: xcachetools_demov1.0\n")
	color.Red("Author:kang\n")
	color.Cyan("-f :   run in daemon eg: -f=xx.conf\n")
}

func ClsTask(w http.ResponseWriter, r *http.Request) {
	var taskid int64
	var xcdnlist = make(map[string]interface{})
	var dllist []string
	var wslist []string
	var isclscdn bool = true

	if r.Method == "POST" {

		result, _ := ioutil.ReadAll(r.Body)
		//log.Println(string(result))
		r.Body.Close()
		js, _ := simplejson.NewJson(result)
		taskid, _ = js.Get("task_id").Int64()
		xkey, _ := js.Get("clean_list").StringArray()
		xtype, _ := js.Get("service_type").String()
		xhost, _ := js.Get("service_list").StringArray()
		xcdnlist, _ = js.Get("cdn_list").Map()

		log.Println("taskid:", taskid)
		go func() {
			switch xtype {
			case "ats", "trafficserver", "TrafficServer", "a":
				ClsAts(xhost, xkey)
			case "ngx", "nginx", "tengine", "n", "tng":
				ClsNgx(xhost, xkey)
			case "hcs", "h":
				ClsHcs(xhost, xkey)
			case "mc", "memcache", "m":
				ClsMc(xhost, xkey)
			case "squid", "s":
				ClsSquid(xhost, xkey)
			case "redis", "r", "rs":
				ClsRedis(xhost, xkey)
			case "CDN", "cdn":
				fmt.Println("only clear CDN")
				ClsDiLian(ProxyIp, DLPushUrl, DLCaptcha, xkey)
				ClsWangSu(ProxyIp, WsUrl, WsUser, WsPass, xkey)
				isclscdn = false
			default:
				clsresult = clsresult + fmt.Sprintf("ERR:no support %s service type\n", xtype)
			}
			//以下为清理cdn
			for key, val := range xcdnlist {
				switch val {
				case "dilian", "dl", "d":
					dllist = append(dllist, key)
				case "wangsu", "ws", "w":
					wslist = append(wslist, key)
				default:
					clsresult = clsresult + fmt.Sprintf("ERR:no support %s this cdn\n", key)
				}
			}

			if isclscdn {
				ClsDiLian(ProxyIp, DLPushUrl, DLCaptcha, dllist)
				ClsWangSu(ProxyIp, WsUrl, WsUser, WsPass, wslist)
			}
			log.Println("clean result:" + clsresult)
			//提交错误信息到接口
			PostResult(clsresult, taskid)
			//返回信息给客户端
			fmt.Fprintf(w, clsresult)
			clsresult = ""
		}()
	} else {
		fmt.Fprintf(w, "no support methold\n")
	}

}

var ListenAddress, ProxyIp, DLPushUrl, DLCaptcha, WsUrl, WsUser, WsPass, InmQuery, clsresult string

//var cfpath = flag.String("f", "", "Specifies the configuration file path")

func main() {
	cfpath := flag.String("f", "", "configuration file path")
	help := flag.Bool("h", false, "show help")
	flag.Parse()
	v := GetConfig(*cfpath)
	ListenAddress = v.ListenAddress
	ProxyIp = v.ProxyIp
	DLPushUrl = v.DLPushUrl
	DLCaptcha = v.DLCaptcha
	WsUrl = v.WsUrl
	WsUser = v.WsUser
	WsPass = v.WsPass
	InmQuery = v.InmQuery
	//ThreadMax = v.ThreadMax

	if *help {
		Help()
		return
	}

	http.HandleFunc("/clean_cache", ClsTask)
	http.ListenAndServe(ListenAddress, nil)
}
